// src/components/ArticleEditor/index.tsx
import React, {
  useState,
  useRef,
  useImperativeHandle,
  forwardRef,
  useEffect,
} from 'react';
import Taro from '@tarojs/taro';
import { View, ScrollView, Image, Textarea } from '@tarojs/components';
import './index.less';
import CommonModal from '../commonModal';

export type BlockType = 'text' | 'image';
export interface Block {
  id: string;
  type: BlockType;
  content: string;
}

export interface ArticleEditorRef {
  insertImage: (url: string) => void;
  insertImages: (urls: string[]) => void;
  getBlocks: () => Block[];
  syncCursorFromNative: () => Promise<void>;
  /** 工具栏 onTouchStart：锁定当前 textarea 光标（配合受控焦点，不做额外书签） */
  prepareInsertAtCursor: () => Promise<boolean>;
}

export interface ArticleEditorProps {
  initialBlocks?: Block[];
  onChange?: (blocks: Block[]) => void;
  maxTextChars?: number;
}

const ArticleEditor = forwardRef<ArticleEditorRef, ArticleEditorProps>(
  ({ initialBlocks, onChange, maxTextChars = 3000 }, ref) => {
    // ---------- 工具 ----------
    const idCounter = useRef(0);
    const genId = () => `${Date.now()}-${idCounter.current++}`;
    const totalTextLen = (bs: Block[]) =>
      bs.reduce(
        (sum, b) => (b.type === 'text' ? sum + (b.content?.length || 0) : sum),
        0
      );
    const createImg = (url: string): Block => ({
      id: genId(),
      type: 'image',
      content: url,
    });

    // [ADD] 短内容"越阈值"最小高度（务必与 .editor-textarea 的 line-height 对齐）
    const LINE_HEIGHT_PX = 24; // ← 如果你的样式是 22/26，请改成一致的值
    const GUARD_LINES = 15; // ←  从10提高到15行，确保3→4行时仍在守护期内
    const GUARD_MIN_HEIGHT = LINE_HEIGHT_PX * GUARD_LINES;

    // 归一化（去掉图片间空文本，末尾最多保留 1 个空文本；结尾是图片则补空文本）
    const normalizeImagesWithoutGaps = (list: Block[]) => {
      const res: Block[] = [];
      for (let i = 0; i < list.length; i++) {
        const b = list[i];
        if (b.type === 'text' && (!b.content || b.content.length === 0)) {
          const prev = res.length ? res[res.length - 1] : undefined;
          const nxt = list[i + 1];
          const betweenTwoImages =
            prev?.type === 'image' && nxt?.type === 'image';
          if (betweenTwoImages) continue;
        }
        res.push(b);
      }
      // 合并尾部相邻空文本
      let t = res.length - 1;
      while (
        t > 0 &&
        res[t]?.type === 'text' &&
        !res[t]?.content &&
        res[t - 1]?.type === 'text' &&
        !res[t - 1]?.content
      ) {
        res.splice(t - 1, 1);
        t--;
      }
      // 结尾是图片则补空文本
      if (res.length && res[res.length - 1].type === 'image') {
        res.push({ id: genId(), type: 'text', content: '' });
      }
      if (res.length === 0)
        res.push({ id: genId(), type: 'text', content: '' });
      return res;
    };

    // ---------- 状态 ----------
    const initBlocks = normalizeImagesWithoutGaps(
      initialBlocks ?? [{ id: genId(), type: 'text', content: '' }]
    );
    const [blocks, setBlocks] = useState<Block[]>(initBlocks);

    type Cursor = { blockId: string; start: number; end: number };
    const [cursor, _setCursor] = useState<Cursor>({
      blockId: initBlocks[0].id,
      start: 0,
      end: 0,
    });
    const cursorRef = useRef(cursor);
    const setCursor = (c: Cursor) => {
      cursorRef.current = c;
      _setCursor(c);
    };

    // 受控焦点：哪个 textarea 在焦点
    const [focusedId, setFocusedId] = useState<string>(initBlocks[0].id);

    // 键盘 & 滚动
    const [kbHeight, setKbHeight] = useState(0);
    const [anchorId, setAnchorId] = useState<string>(''); // 指向 "anchor-xxx"
    const lastKbRef = useRef(0);

    // 短内容守护：在"短内容 + 无图 + 键盘打开"的守护期内，绝不收缩底部占位
    const [shortGuard, setShortGuard] = useState(false);
    const hasAnyImage = (bs: Block[]) => bs.some((b) => b.type === 'image');
    const enterShortGuard = (bs: Block[]) =>
      !hasAnyImage(bs) && totalTextLen(bs) < 150; //  阈值从80提高到150，覆盖更多行
    const releaseShortGuardWhen = (bs: Block[]) =>
      hasAnyImage(bs) || totalTextLen(bs) >= 200; //  满足才释放，从120提高到200

    // "轻抖"聚焦开关：用于强制重新唤起键盘
    const [focusSwitch, setFocusSwitch] = useState(true);
    const reFocus = (id?: string, delay = 100) => {
      //  仅在守护期内且键盘高度仍>0时才自动再聚焦，避免打扰用户主动收起键盘
      if (!shortGuard || lastKbRef.current === 0) return;
      setFocusSwitch(false);
      setTimeout(() => {
        if (id) setFocusedId(id);
        setFocusSwitch(true);
      }, delay);
    };

    // 删除弹窗
    const [showDeleteModel, setShowDeleteModel] = useState(false);
    const [deleteBlockId, setDeleteBlockId] = useState<string | null>(null);

    // blocks 变更回调
    useEffect(() => {
      onChange?.(blocks);
    }, [blocks]);

    // ---------- 键盘高度（仅在 0->0 时滚一次） ----------
    useEffect(() => {
      let raf = 0;
      const onChange = (e: { height: number }) => {
        cancelAnimationFrame(raf);
        raf = requestAnimationFrame(() => {
          const h = Math.max(0, Number(e?.height || 0));
          const opening = lastKbRef.current === 0 && h > 0;
          lastKbRef.current = h;
          setKbHeight(h);

          if (opening && focusedId) {
            // 键盘刚弹起：进入短内容守护（若满足条件）
            setShortGuard((prev) => prev || enterShortGuard(blocks));
            const id = `anchor-${focusedId}`;
            setAnchorId((prev) => (prev === id ? prev : id));
          }
        });
      };
      // @ts-ignore
      Taro.onKeyboardHeightChange?.(onChange);
      return () => {
        // @ts-ignore
        Taro.offKeyboardHeightChange?.(onChange);
        cancelAnimationFrame(raf);
      };
    }, [focusedId, blocks]);

    // ---------- 输入与焦点 ----------
    const handleInput = (e: any, bid: string) => {
      const newVal = String(e?.detail?.value ?? '');
      const newCursorPos =
        typeof e?.detail?.cursor === 'number' ? e.detail.cursor : newVal.length;

      setBlocks((prev) => {
        const old = prev.find((b) => b.id === bid)?.content ?? '';
        const othersLen = totalTextLen(prev) - old.length;
        const budget = Math.max(0, maxTextChars - othersLen);
        const clipped = newVal.slice(0, budget);
        if (clipped.length < newVal.length) {
          Taro.showToast({
            title: `正文最多${maxTextChars}字`,
            icon: 'none',
            duration: 1200,
          });
        }

        const next = prev.map((b) =>
          b.id === bid ? { ...b, content: clipped } : b
        );

        // 检测粘贴/快速增长：满足释放条件时，延后释放守护（避免立刻收缩触发 blur）
        const delta = clipped.length - old.length;
        if (delta >= 30 || releaseShortGuardWhen(next)) {
          //  delta阈值从24提高到30
          if (shortGuard) {
            //  延迟释放从120ms提高到200ms，给系统更多时间稳定布局
            setTimeout(() => {
              setShortGuard(false);
              //  释放后不立即滚动和reFocus，避免打断用户输入
            }, 200);
          }
        }

        return next;
      });

      setCursor({ blockId: bid, start: newCursorPos, end: newCursorPos });
      // 不在 onInput 里滚动，避免与系统/布局竞争
    };

    const handleFocus = (e: any, b: Block) => {
      const text = b.content ?? '';
      const pos =
        typeof e?.detail?.cursor === 'number' ? e.detail.cursor : text.length;
      setFocusedId(b.id);
      setCursor({ blockId: b.id, start: pos, end: pos });

      // 聚焦时：若满足短内容守护条件则进入守护
      setShortGuard((prev) => prev || enterShortGuard(blocks));

      // 滚一次哑锚点
      const id = `anchor-${b.id}`;
      setAnchorId((prev) => (prev === id ? prev : id));
    };

    const handleBlur = (e: any, b: Block) => {
      // 拿得到 cursor 再更新，否则维持旧值
      if (typeof e?.detail?.cursor === 'number') {
        const pos = e.detail.cursor;
        setCursor({ blockId: b.id, start: pos, end: pos });
      }

      // 若还在短内容守护期，且键盘高度记录仍 > 0，多半是"保护性 blur"——尝试温和再聚焦
      if (shortGuard && lastKbRef.current > 0) {
        //  延长延迟从100ms到150ms+120ms，给系统时间完成重排
        setTimeout(() => {
          reFocus(b.id, 120);
        }, 150);
      }
    };

    // ---------- 删除图片 ----------
    const deletePic = () => {
      if (!deleteBlockId) {
        setShowDeleteModel(false);
        return;
      }
      setBlocks((bs) => {
        const filtered = bs.filter((b) => b.id !== deleteBlockId);
        const normalized = normalizeImagesWithoutGaps(filtered);
        let cur = { ...cursorRef.current };
        if (!normalized.find((b) => b.id === cur.blockId)) {
          const lastText = [...normalized]
            .reverse()
            .find((b) => b.type === 'text');
          if (lastText) cur = { blockId: lastText.id, start: 0, end: 0 };
        }
        cursorRef.current = cur;
        _setCursor(cur);

        // 删图后重评估守护
        setShortGuard((prev) => {
          const need = enterShortGuard(normalized);
          return need ? true : prev && !releaseShortGuardWhen(normalized);
        });

        return normalized;
      });
      setDeleteBlockId(null);
      setShowDeleteModel(false);
    };

    // ---------- 插图批处理（按我们维护的光标切分） ----------
    const insertQueueRef = useRef<string[]>([]);
    const flushingRef = useRef(false);

    const scheduleFlush = () => {
      if (flushingRef.current) return;
      flushingRef.current = true;
      Promise.resolve()
        .then(() => flushInsertQueue())
        .finally(() => {
          flushingRef.current = false;
          if (insertQueueRef.current.length) scheduleFlush();
        });
    };

    const flushInsertQueue = () => {
      const urls = insertQueueRef.current.slice();
      insertQueueRef.current = [];
      if (urls.length === 0) return;

      setBlocks((prev) => {
        let next = prev.slice();
        let cur = { ...cursorRef.current };

        for (const url of urls) {
          // 1) 找当前光标块；找不到则落到最后一个文本块
          let idx = next.findIndex((b) => b.id === cur.blockId);
          if (idx < 0) {
            const lastTextIdx = [...next]
              .map((b, i) => [b, i] as const)
              .reverse()
              .find(([b]) => b.type === 'text')?.[1];
            idx =
              typeof lastTextIdx === 'number' ? lastTextIdx : next.length - 1;
            cur.blockId = next[idx]?.id || genId();
            cur.start = 0;
            cur.end = 0;
          }

          const curBlock = next[idx];

          // 2) 如果不是文本块，末尾兜底：图片 + 空文本
          if (!curBlock || curBlock.type !== 'text') {
            const img = createImg(url);
            const tail: Block = { id: genId(), type: 'text', content: '' };
            next = [...next, img, tail];
            cur = { blockId: tail.id, start: 0, end: 0 };
            continue;
          }

          // 3) 文本内切分
          const text = curBlock.content ?? '';
          const start = Math.max(0, Math.min(cur.start ?? 0, text.length));
          const end = Math.max(start, Math.min(cur.end ?? start, text.length));

          if (text.length === 0 || (start === 0 && end === 0)) {
            // 规则A：块首/空块 => 把图片插到该块"前面"
            const img = createImg(url);
            next = [...next.slice(0, idx), img, ...next.slice(idx)];
            cur = { blockId: curBlock.id, start: 0, end: 0 };
            continue;
          }

          // 规则B：中部/末尾 => left | [img] | right（right 即便空也保留）
          const left = text.slice(0, start);
          const right = text.slice(end);
          const leftBlock: Block = { ...curBlock, content: left };
          const img = createImg(url);
          const rightBlock: Block = {
            id: genId(),
            type: 'text',
            content: right,
          };

          next = [
            ...next.slice(0, idx),
            leftBlock,
            img,
            rightBlock,
            ...next.slice(idx + 1),
          ];
          cur = { blockId: rightBlock.id, start: 0, end: 0 };
        }

        // 归一化
        let normalized = normalizeImagesWithoutGaps(next);

        // 若光标块被归一化"挤掉"，落到最后一个文本块
        if (!normalized.find((b) => b.id === cur.blockId)) {
          const lastText = [...normalized]
            .reverse()
            .find((b) => b.type === 'text');
          if (lastText) cur = { blockId: lastText.id, start: 0, end: 0 };
        }

        cursorRef.current = cur;
        _setCursor(cur);

        // 插图：结构变化后滚一次哑锚点，延迟释放短守护避免布局抖动
        Taro.nextTick(() => {
          setAnchorId(`anchor-${cur.blockId}`);
          setFocusedId((prev) => (prev === cur.blockId ? prev : cur.blockId));
          //  延迟释放守护，避免占位突然收缩导致无法唤起键盘
          setTimeout(() => {
            setShortGuard(false);
          }, 300);
        });

        return normalized;
      });
    };

    // ---------- 暴露方法 ----------
    useImperativeHandle(ref, () => ({
      insertImage: (url: string) => {
        insertQueueRef.current.push(url);
        scheduleFlush();
      },
      insertImages: (urls: string[]) => {
        insertQueueRef.current.push(...urls);
        scheduleFlush();
      },
      // 纯 textarea 方案：我们自己维护光标，兼容接口即可
      syncCursorFromNative: async () => {},
      // 工具栏 onTouchStart：此时 textarea 还在焦点，cursorRef 已是最新，直接返回
      prepareInsertAtCursor: async () => {
        const ok = !!cursorRef.current?.blockId;
        return ok;
      },
      getBlocks: () => blocks,
    }));

    // ---------- 渲染 ----------
    return (
      <View className="editor-wrap">
        <ScrollView
          className="editor-container"
          scrollY
          scrollWithAnimation
          enableFlex
          style={{
            height: '100%',
            scrollbarWidth: 'none',
            msOverflowStyle: 'none',
          }}
          scrollIntoView={anchorId}
        >
          {blocks.map((b, i) => (
            <View key={b.id} className="editor-block">
              {b.type === 'text' ? (
                <>
                  {/* 哑锚点：滚它，不滚 textarea 本体 */}
                  <View id={`anchor-${b.id}`} style={{ height: 1 }} />
                  <Textarea
                    id={`ta-${b.id}`}
                    value={b.content}
                    className="editor-textarea"
                    autoHeight
                    // 受控焦点：谁是 focusedId，谁的 focus=true（通过 focusSwitch 轻抖）
                    focus={focusedId === b.id && focusSwitch}
                    showConfirmBar={true}
                    adjustPosition={false} // 统一由我们控制滚动
                    cursorSpacing={64}
                    maxlength={-1}
                    placeholder={
                      i === 0 ? '点击输入正文，记录这一刻~' : '继续输入…'
                    }
                    onFocus={(e: any) => handleFocus(e, b)}
                    onBlur={(e: any) => handleBlur(e, b)}
                    onInput={(e: any) => handleInput(e, b.id)}
                    // 注意：不在 onLineChange 里立刻"关闭占位"，避免 3→4 行临界触发收键盘
                    onLineChange={(e: any) => {
                      const lc = Number(e?.detail?.lineCount || 0);
                      // 行数仅用来早期释放判断，不直接改 shortGuard
                      //  用 GUARD_LINES+3 作为释放阈值，确保3→4行时完全不触发
                      if (shortGuard && lc >= GUARD_LINES + 3) {
                        //  达到阈值后温和释放，延迟从120ms提高到300ms
                        setTimeout(() => {
                          setShortGuard(false);
                          //  不再做滚动和reFocus，避免打断用户
                        }, 300);
                      }
                    }}
                    // [ADD] 仅在"短内容守护 + 当前块在焦点 + 键盘已弹起"时抬高最小高度（一次性跨过第4行临界）
                    style={
                      shortGuard && focusedId === b.id && kbHeight > 0
                        ? { minHeight: `${GUARD_MIN_HEIGHT}px` }
                        : undefined
                    }
                  />
                </>
              ) : (
                <View className="editor-img-box">
                  <Image
                    src={b.content}
                    className="editor-img"
                    mode="aspectFill"
                  />
                  <Image
                    src="https://file.linkcook.cn/image/wineMiniProgram/home/delete.png"
                    className="delete-icon"
                    onClick={() => {
                      setDeleteBlockId(b.id);
                      setShowDeleteModel(true);
                    }}
                  />
                </View>
              )}
            </View>
          ))}

          {/* 底部占位：守护期内始终给足（不收缩），避免第 4 行触发保护性 blur */}
          {/*  守护期内使用固定高度400px，避免任何高度变化引发重排 */}
          {/*  非守护期也要给足够的占位，避免内容多时键盘遮挡导致无法唤起 */}
          <View
            style={{
              height: focusedId
                ? shortGuard
                  ? '400px'
                  : Math.max(kbHeight, 360) + 'px' //  最小360px，确保键盘不会遮挡
                : '0px',
            }}
          />
        </ScrollView>

        <CommonModal
          visible={showDeleteModel}
          title="确定删除该图片吗？"
          okText="确定"
          okClick={deletePic}
          cancelText="取消"
          cancelClick={() => setShowDeleteModel(false)}
        />
      </View>
    );
  }
);

export default ArticleEditor;
